//*********************************************************
//
//    Copyright (c) Microsoft. All rights reserved.
//    This code is licensed under the Apache License, Version 2.0.
//    THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF
//    ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY
//    IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR
//    PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT.
//
//*********************************************************

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO.Packaging;
using System.Xml;
using System.IO;

namespace Microsoft.Research.DataLayer
{
    /// <summary>
    /// 
    /// </summary>
    public class OCPDataProductHandler
    {
        /// <summary>
        /// 
        /// </summary>
        /// <param name="obj1"></param>
        /// <param name="field"></param>
        /// <param name="obj2Type"></param>
        /// <param name="creator"></param>
        /// <param name="myPackage"></param>
        /// <param name="conn"></param>
        /// <param name="results"></param>
        public static void GetDataProductContents(IObject obj1, string field, Connection.Storage obj2Type,
            Connection.ObjectCreator creator, Package myPackage, OPCConnection conn, out List<IObject> results)
        {
            results = new List<IObject>();

            try
            {
                // Get the part which contains the details about the blob kind
                string assemblyBlobUri = String.Concat("/Trident/DataProductKind/", obj1.ID.ToString(), "/ImageBlob.xml");
                XmlDocument blobXmlPart = conn.GetPartAsXml(myPackage.GetPart(new Uri(assemblyBlobUri, UriKind.Relative)));

                // Load the details about the blob in a new document node.
                XmlDocument doc = new XmlDocument();
                doc.LoadXml(blobXmlPart.OuterXml);
                XmlNode xmlElemNode = doc.FirstChild;


                XmlNode assemblyImageNode = conn.GetPartFromId(String.Concat("/Trident/DataProductKind/", obj1.ID.ToString(), ".xml"))[0];
                if (null == assemblyImageNode)
                {
                }

                XmlNode chunks = xmlElemNode.SelectSingleNode("Chunks");
                XmlNode chunkSizeNode = xmlElemNode.SelectSingleNode("ChunkSize");

                int iNumChunks = Convert.ToInt32(chunks.InnerText);
                

                string imageUri = String.Concat("/Trident/DataProductKind/", obj1.ID.ToString(), "/Blob");
                PackagePart assemblyPart = myPackage.GetPart(new Uri(imageUri, UriKind.Relative));

                // Calculate the size of the each chunk. The last part will 
                // have the number of bytes that may not be eqaul to the chunk size.
                Stream imageStream = assemblyPart.GetStream();
                int chunkSize = Convert.ToInt32(chunkSizeNode.InnerText); // (int)imageStream.Length / iNumChunks;
                //int lastPartSize = (int)imageStream.Length % iNumChunks;
                int bytesRead = 0;

                List<XmlNode> imageChunks = new List<XmlNode>();
                for (int i = 0; i < iNumChunks; ++i)
                {
                    XmlNode chunk = doc.CreateNode(XmlNodeType.Element, "ObjectDetails", "");
                    XmlNode contents = doc.CreateNode(XmlNodeType.Element, "Contents", "");
                    XmlNode createdBy = doc.CreateNode(XmlNodeType.Element, "CreatedBy", "");
                    XmlNode createdDate = doc.CreateNode(XmlNodeType.Element, "CreatedDate", "");
                    chunk.InnerXml = xmlElemNode.InnerXml;
                    createdBy.InnerXml = Environment.UserName;
                    createdDate.InnerXml = DateTime.Now.ToString();

                    byte[] arr = null;
                    if (i + 1 == iNumChunks)
                        arr = new byte[imageStream.Length - bytesRead];
                    else
                        arr = new byte[chunkSize];
                    imageStream.Read(arr, 0, arr.Length);
                    contents.InnerText = Convert.ToBase64String(arr);
                    chunk.AppendChild(contents);
                    chunk.AppendChild(createdBy);
                    chunk.AppendChild(createdDate);
                    chunk.SelectSingleNode("Index").InnerText = i.ToString();

                    imageChunks.Add(chunk);
                    bytesRead += chunkSize;
                }

                OPCDataReader reader = new OPCDataReader(imageChunks);
                while (reader.Read())
                {
                    results.Add(creator(reader, conn));
                }
            }
            catch (Exception)
            {
                throw new RecordNotFoundException("Error while updating AssemblyImage Blob Part.");
            }
        }

        private static void InsertDataProductBlobElem(Package myPackage, string uri, long chunkSize, ref List<Parameter> paramList)
        {
            Uri partUri = new Uri(uri, UriKind.Relative);

            // If the xml part exists then do not create new
            try
            {
                if (true == myPackage.PartExists(partUri))
                    return;
            }
            catch (Exception)
            { }

            XmlDocument doc = new XmlDocument();
            XmlNode objectNode = doc.CreateNode(XmlNodeType.Element, "ObjectDetails", "");

            foreach (Parameter param in paramList)
            {
                if (param.Value != null && (param.Name.ToUpper() != "CONTENTS") &&
                    !(param.Value is DateTime && param.Value.Equals(DateTime.MinValue)))
                {
                    XmlNode paramNode = doc.CreateNode(XmlNodeType.Element, param.Name, "");
                    paramNode.InnerText = param.Value.ToString();

                    XmlAttribute paramType = doc.CreateAttribute("Type");
                    if (param.Name != "Contents")
                        paramType.Value = param.Type.ToString();
                    paramNode.Attributes.Append(paramType);

                    objectNode.AppendChild(paramNode);
                }
            }

            XmlNode chunkNode = doc.CreateNode(XmlNodeType.Element, "ChunkSize", "");
            chunkNode.InnerText = chunkSize.ToString();
            objectNode.AppendChild(chunkNode);

            doc.AppendChild(objectNode);
            System.Text.ASCIIEncoding encoding = new System.Text.ASCIIEncoding();
            byte[] data = encoding.GetBytes(doc.OuterXml);

            PackagePart part = myPackage.CreatePart(partUri, "trident/tempobject");
            part.GetStream().Write(data, 0, data.Length);

            myPackage.Flush();
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="myPackage"></param>
        /// <param name="obj1"></param>
        /// <param name="obj2"></param>
        /// <param name="objId"></param>
        /// <param name="objKind"></param>
        /// <param name="conn"></param>
        /// <param name="paramList"></param>
        public static void SaveDataProductContents(Package myPackage, Connection.Storage obj1, Connection.Storage obj2,
                object objId, string objKind, OPCConnection conn, ref List<Parameter> paramList)
        {
            Parameter idParam = Parameter.GetParamByName("DataProductID", paramList);
            Parameter numChunks = Parameter.GetParamByName("Chunks", paramList);
            Parameter currentChunk = Parameter.GetParamByName("Index", paramList);
            Parameter contents = Parameter.GetParamByName("Contents", paramList);

            int iNumChunks = Convert.ToInt32(numChunks.Value);
            int iCurrChunk = Convert.ToInt32(currentChunk.Value);

            if (null == idParam)
                throw new RecordNotFoundException("Data Product does not exist.");

            string elemUri = String.Concat("/Trident/DataProductKind/" + idParam.Value.ToString() + ".xml");
            XmlNode node = conn.GetPartFromId(elemUri)[0];

            string imageUri = String.Concat("/Trident/DataProductKind/", idParam.Value.ToString(), "/Blob");
            PackagePart assemblyContent = null;

            if (false == myPackage.PartExists(new Uri(imageUri, UriKind.Relative)))
            {
                assemblyContent = myPackage.CreatePart(new Uri(imageUri, UriKind.Relative), System.Net.Mime.MediaTypeNames.Application.Octet);
            }
            else
                assemblyContent = myPackage.GetPart(new Uri(imageUri, UriKind.Relative));

            byte[] data = Convert.FromBase64String(contents.Value.ToString());
            Stream assemblyStream = assemblyContent.GetStream();
            if (iNumChunks == (iCurrChunk + 1))
                assemblyStream.Seek(assemblyStream.Length, SeekOrigin.Begin);
            else
            {
                assemblyStream.Seek((iCurrChunk) * (data.Length), SeekOrigin.Begin);
            }

            assemblyStream.Write(data, 0, data.Length);
            myPackage.Flush();

            // Now write the details about the assembly image too as this contains
            // details like number of chunks etc.
            string assemblyBlobUri = String.Concat("/Trident/DataProductKind/", idParam.Value.ToString(), "/ImageBlob.xml");
            InsertDataProductBlobElem(myPackage, assemblyBlobUri, data.Length, ref paramList);
        }
    }
}
